P05-2. L7 트래픽 엔지니어링과 서킷 브레이커 - 실습 검증

Part 05 (실습): L7 트래픽 엔지니어링과 서킷 브레이커 검증

실습 목표

  1. order-api의 신규 버전인 order-api-v2 파드를 배포하고, 쿠버네티스 표준 Gateway API (HTTPRoute) 를 Waypoint Proxy에 연결하여 정확히 90:10 비율의 카나리 가중치 분산을 실측한다.
  2. 사내 테스터 전용 헤더(x-canary: true)가 포함된 요청은 가중치와 무관하게 100% v2 파드로만 라우팅되는 헤더 기반 분기를 검증한다.
  3. 카오스 엔지니어링을 위한 결함 주입(Fault Abort 50% HTTP 500 & Fault Delay 3초 지연) 을 실행해 응답 헤더와 지연 시간을 확인한다.
  4. DestinationRule 서킷 브레이커(connectionPool) 를 설정하고 동시 병렬 요청(curl --parallel)을 쏘아, 용량 초과 시 백엔드 파드를 보호하기 위해 프록시가 즉시 HTTP 503 Service Unavailable (x-envoy-overloaded: true, reset reason: overflow) 로 차단하는 현장을 포착한다.

사전 환경 확인

  • P05-1 이론 개념을 먼저 읽고 오면 카나리 배포, HTTPRoute vs VirtualService, DestinationRule의 서킷 브레이커 원리가 명확히 이해됩니다.
  • Part 04 실습 마지막 단계에서 인가 정책(AuthorizationPolicy)을 정리한 상태(part1-demo에 order-api, study-client, waypoint가 떠 있는 상태)에서 바로 이어서 진행합니다.
  • Windows PowerShell 창을 열고 아래 명령어로 현재 파드 상태를 확인합니다.
    $env:Path += ";$HOME\bin"
    cd "$HOME\mesh-study"
    kubectl get pods -n part1-demo
    


실습 아키텍처 및 트래픽 제어 흐름 한눈에 보기

flowchart TB
    subgraph STEP1_2["Step 1~2 · Gateway API HTTPRoute 기반 카나리 & 헤더 분기"]
        direction LR
        C1["study-client"] --> WP1["Waypoint Proxy (Envoy L7)"]
        WP1 -->|"1. x-canary: true 헤더 있음"| V2_ONLY["order-api-v2 (100%)"]
        WP1 -->|"2. 일반 요청 (Weight 90)"| V1_90["order-api-v1 (파드 2개, 90%)"]
        WP1 -->|"3. 일반 요청 (Weight 10)"| V2_10["order-api-v2 (파드 1개, 10%)"]
    end
    subgraph STEP3_4["Step 3~4 · Fault Injection & DestinationRule 서킷 브레이커"]
        direction LR
        C2["study-client
(5개 동시 병렬 요청!)"] --> WP2["Waypoint Proxy
(maxConnections: 1
http1MaxPendingRequests: 1)"] WP2 -->|"수용 한도 이내 (1~2개)"| OK["200 OK 정상 처리"] WP2 -->|"한도 초과 폭주분!"| CB["즉시 차단! 503 Service Unavailable
x-envoy-overloaded: true
(reset reason: overflow / UO)"] end

Step 1. 신규 버전(order-api-v2) 배포 및 HTTPRoute 카나리 규칙 적용

현재 part1-demo에는 version: v1 라벨을 가진 order-api 파드 2개가 떠 있습니다.
여기에 신규 버전인 order-api-v2 디플로이먼트(파드 1개) 와 각 버전을 가리키는 서브 서비스(order-api-v1, order-api-v2), 그리고 쿠버네티스 표준 HTTPRoute (order-api-canary) 를 배포합니다.

HTTPRoute의 parentRefs가 어떻게 Waypoint와 연결되나요?

일반 인그레스 게이트웨이에서는 parentRefs에 kind: Gateway를 적지만, Ambient Mesh 내부(동서 트래픽) 라우팅에서는 parentRefs에 대표 서비스(kind: Service, name: order-api)를 직접 지정합니다! 그러면 그 서비스를 담당하는 Waypoint Proxy가 자동으로 이 HTTPRoute 규칙을 읽어 들여 작동합니다.

# part5-canary.yaml 생성 및 적용
@"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api-v2
  namespace: part1-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: order-api
      version: v2
  template:
    metadata:
      labels:
        app: order-api
        version: v2
    spec:
      containers:
      - name: echo
        image: mendhak/http-https-echo:35
        ports:
        - containerPort: 8080
        env:
        - name: HTTP_PORT
          value: "8080"
---
apiVersion: v1
kind: Service
metadata:
  name: order-api-v1
  namespace: part1-demo
spec:
  selector:
    app: order-api
    version: v1
  ports:
  - name: http
    port: 80
    targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-api-v2
  namespace: part1-demo
spec:
  selector:
    app: order-api
    version: v2
  ports:
  - name: http
    port: 80
    targetPort: 8080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: order-api-canary
  namespace: part1-demo
spec:
  parentRefs:
  - group: ""
    kind: Service
    name: order-api
    port: 80
  rules:
  - matches:
    - headers:
      - name: x-canary
        value: "true"
    backendRefs:
    - name: order-api-v2
      port: 80
  - backendRefs:
    - name: order-api-v1
      port: 80
      weight: 90
    - name: order-api-v2
      port: 80
      weight: 10
"@ | Set-Content -Path "$HOME\mesh-study\part5-canary.yaml" -Encoding utf8

kubectl apply -f "$HOME\mesh-study\part5-canary.yaml"
kubectl wait --for=condition=Ready pod -l version=v2 -n part1-demo --timeout=90s

실제 출력 결과:

deployment.apps/order-api-v2 created
service/order-api-v1 created
service/order-api-v2 created
httproute.gateway.networking.k8s.io/order-api-canary created
pod/order-api-v2-677cff8fd5-wz8sp condition met

Step 2. 90:10 카나리 가중치 분산 및 x-canary: true 헤더 분기 실측!

만약 기본 쿠버네티스 Service였다면 현재 v1 파드가 2개, v2 파드가 1개이므로 20번 요청을 보내면 v2가 33.3%(약 6~7번)를 가져갔을 것입니다.
과연 우리의 Waypoint Proxy가 HTTPRoute에 적힌 weight: 90 vs weight: 10 규칙대로 20번 중 딱 2번(10%)만 v2로 보내는지 확인해 봅니다!

# 1. 일반 고객 요청 20회 연속 전송 후 응답 파드별 횟수(Count) 집계!
kubectl exec -n part1-demo study-client -- curl -s "http://order-api/canary?[1-20]" | Select-String '"hostname": "order-api-' | Group-Object Line | Select-Object Count, Name

# 2. 사내 QA 테스터 헤더(-H "x-canary: true")를 붙여 4회 연속 전송!
kubectl exec -n part1-demo study-client -- curl -s -H "x-canary: true" "http://order-api/canary?[1-4]" | Select-String '"hostname": "order-api-' | ForEach-Object { $_.Line }

실제 출력 결과:

# 1. 일반 요청 20회 집계 결과 (v1 합계: 11 + 7 = 18회 [90%], v2: 2회 [10%]!)
Count Name                                           
----- ----                                           
   11     "hostname": "order-api-86d8bb68dd-xh5rm"   
    2     "hostname": "order-api-v2-677cff8fd5-wz8sp"
    7     "hostname": "order-api-86d8bb68dd-mkvrw"   

# 2. x-canary: true 헤더 요청 4회 결과 (4회 모두 100% v2 파드로만 이동!)
    "hostname": "order-api-v2-677cff8fd5-wz8sp"
    "hostname": "order-api-v2-677cff8fd5-wz8sp"
    "hostname": "order-api-v2-677cff8fd5-wz8sp"
    "hostname": "order-api-v2-677cff8fd5-wz8sp"

Step 3. 결함 주입(Fault Injection)과 서킷 브레이커(DestinationRule) 배포

이번에는 카오스 엔지니어링과 서킷 브레이커를 검증하기 위해 기존 HTTPRoute를 지우고, Istio의 VirtualService 와 DestinationRule 을 배포합니다.

# 1. 기존 HTTPRoute 삭제 (VirtualService와 충돌 방지)
kubectl delete httproute order-api-canary -n part1-demo

# 2. VirtualService(결함 주입) + DestinationRule(서킷 브레이커) 생성 및 적용
@"
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: order-api-resilience
  namespace: part1-demo
spec:
  hosts:
  - order-api
  http:
  - match:
    - uri:
        prefix: /slow
    fault:
      delay:
        percentage:
          value: 100
        fixedDelay: 3s
    route:
    - destination:
        host: order-api-v1
  - match:
    - uri:
        prefix: /chaos
    fault:
      abort:
        percentage:
          value: 50
        httpStatus: 500
    route:
    - destination:
        host: order-api-v1
  - route:
    - destination:
        host: order-api-v1
      weight: 90
    - destination:
        host: order-api-v2
      weight: 10
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: order-api-cb
  namespace: part1-demo
spec:
  host: order-api
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 1
      http:
        http1MaxPendingRequests: 1
        maxRequestsPerConnection: 1
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: order-api-v1-cb
  namespace: part1-demo
spec:
  host: order-api-v1
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 1
      http:
        http1MaxPendingRequests: 1
        maxRequestsPerConnection: 1
"@ | Set-Content -Path "$HOME\mesh-study\part5-resilience.yaml" -Encoding utf8

kubectl apply -f "$HOME\mesh-study\part5-resilience.yaml"

Step 4. 카오스 결함 주입(500 Abort) 및 서킷 브레이커(503 Overflow) 폭발 실측!

먼저 /chaos 경로를 6번 호출해 정말로 절반 가량이 fault filter abort(HTTP 500) 로 떨어지는지 확인하고, 이어서 3초씩 걸리는 /slow 경로에 curl --parallel --parallel-max 5 로 5개의 요청을 동시에 폭격하여 두꺼비집(서킷 브레이커)이 내려가는 장면을 포착합니다!

# 1. /chaos 경로 6회 호출 -> 50% 확률로 HTTP 500 (fault filter abort) 발생 확인!
kubectl exec -n part1-demo study-client -- curl -s -i "http://order-api/chaos?[1-6]" | Select-String "HTTP/1.1|fault filter abort"

# 2. /slow 경로에 5개 동시 병렬 요청(--parallel) 전송 -> 서킷 브레이커 작동(503 x-envoy-overloaded) 확인!
kubectl exec -n part1-demo study-client -- curl -s -i --parallel --parallel-max 5 "http://order-api/slow?[1-5]" | Select-String "HTTP/1.1|x-envoy-overloaded|overflow"

실제 출력 결과:

# 1. /chaos 50% 강제 에러 주입 결과
HTTP/1.1 500 Internal Server Error
fault filter abortHTTP/1.1 200 OK
}HTTP/1.1 500 Internal Server Error
fault filter abortHTTP/1.1 200 OK
}HTTP/1.1 200 OK
}HTTP/1.1 200 OK

# 2. /slow 동시 5개 병렬 요청 시 서킷 브레이커 작동 결과!
HTTP/1.1 200 OK
}HTTP/1.1 200 OK
}HTTP/1.1 503 Service Unavailable
x-envoy-overloaded: true
upstream connect error or disconnect/reset before headers. reset reason: overflowHTTP/1.1 503 Service Unavailable
x-envoy-overloaded: true
upstream connect error or disconnect/reset before headers. reset reason: overflowHTTP/1.1 200 OK
서킷 브레이커가 서버를 구한 결정적 증거 해부!

5개의 요청이 동시에 몰려들었을 때, 수용 한도를 넘어선 2개의 요청에 대해 Waypoint Proxy는 백엔드 파드로 요청을 억지로 밀어 넣지 않고 즉시 다음과 같은 응답과 함께 두꺼비집을 내렸습니다:

  • 상태 코드: HTTP/1.1 503 Service Unavailable
  • 응답 헤더: x-envoy-overloaded: true (Envoy의 커넥션 풀 한도가 초과되어 서킷 브레이커가 작동했다는 공식 마커!)
  • 응답 본문: upstream connect error or disconnect/reset before headers. reset reason: overflow (액세스 로그의 UO — Upstream Overflow 플래그와 1:1 매칭!)
    덕분에 한도 내에 들어온 나머지 요청들은 3초 뒤 모두 무사히 HTTP/1.1 200 OK 를 반환받으며 백엔드 서버가 죽지 않고 살아남았습니다!

다음 파트를 위한 리소스 정리

Part 06 실습에 영향을 주지 않도록 방금 테스트한 결함 주입과 서킷 브레이커 규칙을 삭제합니다.

kubectl delete virtualservice,destinationrule --all -n part1-demo

Step 5. 자주 발생하는 문제와 해결 (Troubleshooting)

증상 / 에러 메시지 원인 해결 방법
HTTPRoute를 적용했는데 가중치(90:10)가 안 먹고 기존처럼 반반씩 나뉨 parentRefs에 kind: Gateway를 적었거나, 해당 네임스페이스/서비스에 Waypoint가 연결되지 않음 Ambient Mesh 동서 트래픽 제어 시 HTTPRoute의 parentRefs에는 반드시 대표 kind: Service(name: order-api) 를 지정해야 함
curl "http://order-api/slow?[1-5]" 를 쳤는데 서킷 브레이커(503)가 안 터지고 5개 모두 200 OK가 나옴 --parallel 옵션이 없으면 curl은 1번 요청이 끝난 뒤에야 2번 요청을 순차적으로(1개씩) 보내므로 동시 연결 한도(maxConnections: 1)를 넘지 않음 반드시 --parallel --parallel-max 5 옵션을 붙여 5개의 요청이 동시에 들어가도록 해야 서킷 브레이커가 작동함
PowerShell에서 Group-Object 실행 후 다음 Select-String 출력이 빈칸으로 보임 한 스크립트 블록 안에서 앞선 Group-Object의 테이블 포맷터가 뒤쪽 MatchInfo 객체 출력까지 묶어버림 뒤쪽 명령 끝에 | ForEach-Object { $_.Line } 를 붙여 문자열로 꺼내면 깔끔하게 출력됨

Step 6. 이번 파트 핵심 명령어 정리 (Command Reference)

# 1. 현재 적용된 Gateway API HTTPRoute 및 Istio VirtualService/DestinationRule 확인
kubectl get httproute,virtualservice,destinationrule -n part1-demo

# 2. 20회 요청 전송 후 응답 파드 비율(Count) 집계
kubectl exec -n part1-demo study-client -- curl -s "http://order-api/canary?[1-20]" | Select-String '"hostname": "order-api-' | Group-Object Line | Select-Object Count, Name

# 3. 동시 병렬 요청(--parallel)으로 서킷 브레이커(x-envoy-overloaded) 작동 테스트
kubectl exec -n part1-demo study-client -- curl -s -i --parallel --parallel-max 5 "http://order-api/slow?[1-5]" | Select-String "HTTP/1.1|x-envoy-overloaded|overflow"

Step 7. 셀프 체크 (Self-Check Q&A)

Q1. 기본 쿠버네티스 Service 환경에서 v1 파드 2개와 v2 파드 1개가 떠 있을 때 트래픽을 90:10으로 나눌 수 없는 이유는 무엇이며, Ambient Mesh는 이를 어떻게 해결하나요?

Q2. DestinationRule 서킷 브레이커가 동시 요청 폭주를 감지해 요청을 즉시 차단할 때 반환하는 HTTP 상태 코드, 특수 응답 헤더, 그리고 Envoy 로그 플래그는 무엇인가요?


🔗 다음 파트로 이동

지금까지 우리는 쿠버네티스 클러스터 안쪽에 있는 파드들을 자유자재로 다루었습니다.
하지만 실제 기업 인프라(KT Cloud, NCP, AWS, 온프레미스)에서는 데이터베이스(PostgreSQL)나 레거시 결제 서버가 쿠버네티스 파드가 아닌 외부의 실제 Linux VM(가상머신) 에 떠 있는 경우가 대부분입니다!
다음 Part 06에서는 쿠버네티스 바깥의 Docker 브릿지 네트워크에 실제 PostgreSQL 18 Linux VM 컨테이너(study-pg-vm) 를 띄우고, 이를 WorkloadGroup · WorkloadEntry · ServiceEntry 로 서비스 메시 안에 정식 가족으로 편입시키는 3세대 하이브리드 서비스 메시를 구축합니다!


네비게이션

이전 목록 다음
◀ Part 05 이론 목록으로 Part 06 이론 ▶